/*
Copyright (C) 2015 Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <mapDisplay.h>
#include <normalizer.h>
#include <iostream>

using namespace sf;

MapDisplayer::MapDisplayer(Vector2u size
			   , MapDisplayType mapDisplayType)
{
    this->size = size;
    this->mapDisplayType = mapDisplayType;

    heightmapCopy = new double[size.x * size.y];
    heightmapImage.create(size.x, size.y);
    simpleMapImage.create(size.x, size.y);
    landMapImage.create(size.x, size.y);
    waterImage.create(size.x, size.y);
    lightMapImage.create(size.x, size.y);
    smoothingType = SMOOTHING_TYPE_AVERAGE;

    islandNameText.setPosition(20, 0);
    islandNameText.setCharacterSize(80);
    islandNameText.setColor(Color(0, 0, 0));
}

void MapDisplayer::setHeightmap(double *heightmap)
{
    this->heightmap = heightmap;
}

void MapDisplayer::setWatermap(double *watermap)
{
    this->normalizedWatermap = watermap;
}

void MapDisplayer::setMapDisplayType(MapDisplayType mapDisplayType)
{
    this->mapDisplayType = mapDisplayType;
}

MapDisplayType MapDisplayer::getMapDisplayType()
{
    return mapDisplayType;
}

void MapDisplayer::updateDisplay()
{
    switch (mapDisplayType) {
    case MAP_DISPLAY_TYPE_HEIGHTMAP:
	updateHeightmapDisplay();
	break;
    case MAP_DISPLAY_TYPE_SIMPLE_MAP:
	updateSimpleMapDisplay();
	break;
    case MAP_DISPLAY_TYPE_LAND_MAP:
	updateLandMapDisplay();
	break;
    case MAP_DISPLAY_TYPE_LIGHTED_MAP:
        updateLightMapDisplay();
        break;
    }

    if (displayWater)
        updateWaterDisplay();
}

void MapDisplayer::updateWaterDisplay()
{
    for (int x = 0; x < size.x; x++)
    {
        for (int y = 0; y < size.y; y++)
        {
            Color waterColor(0, 0, 200, 0);
            int transparency = normalizedWatermap[x + y * size.x] * 255;
            transparency = transparency > 0 ? 255 : 0;
            transparency = transparency > 255 ? 255 : transparency;
            waterColor.a = transparency;
            waterImage.setPixel(x, y, waterColor);
        }
        //qstd::cout << normalizedWatermap[x + 1 * size.x] << ",";
    }

    waterTexture.loadFromImage(waterImage);
    waterSprite.setTexture(waterTexture);
}

void MapDisplayer::updateHeightmapDisplay()
{
    Normalizer<double>::normalize(heightmap, heightmapCopy, size.x * size.y,
                                  0.0, 1.0);

    // Interpolate the missing data
    if (passSize > 1)
	for (int outerx = 0; outerx < size.x - 1; outerx += passSize)
	    for (int outery = 0; outery < size.y - 1; outery+= passSize) {
		double topLeft = heightmapCopy
		    [outerx + outery * size.x]
		    , topRight = heightmapCopy
		    [outerx + passSize + outery * size.x]
		    , bottomLeft = heightmapCopy
		    [outerx + (passSize + outery) * size.x]
		    , bottomRight = heightmapCopy
		    [outerx + passSize + (passSize + outery) * size.x];
		for (int x = 0; x < passSize; x++)
		    for (int y = 0; y < passSize; y++) {
			if (smoothingType
			    == SMOOTHING_TYPE_INTERPOLATE) {
			    double ratioH = (double) (x)
				/ (double) (passSize)
				, ratioV = (double) (y)
				/ (double) (passSize)
				, top = ((1.0 - ratioH) * topLeft)
				+ (ratioH * topRight)
				, bottom = ((1.0 - ratioH) * bottomLeft)
				+ (ratioH * bottomRight);
			    heightmapCopy
				[(outerx + x) + (outery + y) * size.x]
				= top * (1.0 - ratioV)
				+ bottom * ratioV;
			} else if (smoothingType
				   == SMOOTHING_TYPE_AVERAGE) {
			    heightmapCopy
				[(outerx + x) + (outery + y) * size.x]
				= (topLeft + topRight + bottomLeft + bottomRight)
				/ 4.0;
			}
		    }
	    }

    // Translate the normalized values to the sf::Image
    for (int x = 0; x < size.x; x++) {
	for (int y = 0; y < size.y; y++) {
	    int whiteVal = 255.0
		* heightmapCopy[x + y * size.x];
	    heightmapImage.setPixel
		(x, y, Color(whiteVal, whiteVal, whiteVal));
	}
    }

    // Update the texture and the sprite
    displayTexture.loadFromImage(heightmapImage);
    displaySprite.setTexture(displayTexture);
}

void MapDisplayer::updateSimpleMapDisplay()
{
    updateHeightmapDisplay();

    for (int x = 0; x < size.x; x++)
	for (int y = 0; y < size.y; y++)
	    simpleMapImage.setPixel
		(x, y, getSimpleMapColor(heightmapCopy
					 [x + y * size.x]));

    // Update the texture and the sprite
    displayTexture.loadFromImage(simpleMapImage);
    displaySprite.setTexture(displayTexture);
}

void MapDisplayer::updateLandMapDisplay()
{
    for (unsigned x = 0; x < size.x; x++)
        for (unsigned y = 0; y < size.y; y++)
            landMapImage.setPixel(x, y, landMap[x + y * size.x] ?
                                  heightmapImage.getPixel(x, y) :
                                  COLOR_WATER);

    // Update the texture and the sprite
    displayTexture.loadFromImage(landMapImage);
    displaySprite.setTexture(displayTexture);
}

void MapDisplayer::updateLightMapDisplay()
{
    updateSimpleMapDisplay();
    for (unsigned x = 0; x < size.x; x++)
        for (unsigned y = 0; y < size.y; y++) {
            Color mapColor = simpleMapImage.getPixel(x, y);
            mapColor.r = std::min(255, (int) (((float) mapColor.r) *
                                              lightmap[x + y * size.x]
                                              .x));
            mapColor.g = std::min(255, (int) (((float) mapColor.g) *
                                              lightmap[x + y * size.x]
                                              .y));
            mapColor.b = std::min(255, (int) (((float) mapColor.b) *
                                              lightmap[x + y * size.x]
                                              .z));
            lightMapImage.setPixel(x, y, mapColor);
        }

    // Update the texture and the sprite
    displayTexture.loadFromImage(lightMapImage);
    displaySprite.setTexture(displayTexture);
}

Color MapDisplayer::getSimpleMapColor(double value)
{
    if (value < waterLevel) {
	return COLOR_WATER;
    } else {
	double aboveWater = (value - waterLevel)
	    / (1.0 - waterLevel);
	if (ONE_LAND_COLOR)
	    return Color(0, 100, 0);
	if (aboveWater < getModifiedValue(0.06))
	    return COLOR_BEACH;
	else if (aboveWater < getModifiedValue(0.35))
	    return COLOR_GRASS;
	else if (aboveWater < getModifiedValue(0.5))
	    return COLOR_FOREST;
	else if (aboveWater < getModifiedValue(0.75))
	    return COLOR_MOUNTAIN;
	else
	    return COLOR_ICE;
    }
}

double MapDisplayer::getModifiedValue(double aboveWater)
{
    double value = aboveWater * (1.0 - 0.45) + 0.45; // Unmodified waterlevel
    double modifiedValue = modifierFunction(value);
    return ((modifiedValue - waterLevel) / (1.0 - waterLevel));
}

void MapDisplayer::setHeightmapPassSize(int passSize)
{
    this->passSize = passSize;
}

void MapDisplayer::setSmoothingType(SmoothingType smoothingType)
{
    this->smoothingType = smoothingType;
}

void MapDisplayer::setWaterLevel(double waterLevel)
{
    this->waterLevel = waterLevel;
}

void MapDisplayer::setHeightModifierFunction(
    double (*modifierFunction)(double))
{
    this->modifierFunction = modifierFunction;
}

void MapDisplayer::setNamesFont(Font *namesFont)
{
    islandNameText.setFont(*namesFont);
}

void MapDisplayer::setIslandName(std::string islandName)
{
    islandNameText.setString(islandName);
    std::cout << islandName << std::endl;
}

void MapDisplayer::setLandMap(bool *landMap)
{
    this->landMap = landMap;
}

void MapDisplayer::setLightMap(Vec3f *lightmap)
{
    this->lightmap = lightmap;
}

void MapDisplayer::draw(RenderWindow *window)
{
    switch (mapDisplayType) {
    case MAP_DISPLAY_TYPE_HEIGHTMAP:
	window->draw(displaySprite);
	break;
    case MAP_DISPLAY_TYPE_SIMPLE_MAP:
	window->draw(displaySprite);
	window->draw(islandNameText);
	break;
    case MAP_DISPLAY_TYPE_LAND_MAP:
        window->draw(displaySprite);
        window->draw(islandNameText);
        break;
    case MAP_DISPLAY_TYPE_LIGHTED_MAP:
        window->draw(displaySprite)
            ;
    }

    if (displayWater)
        window->draw(waterSprite);
}
